package com.zzh.lib.log;

import static com.zzh.lib.log.Logger.MSG_FLUSH_LOG;
import static com.zzh.lib.log.Logger.MSG_RELEASE;
import static com.zzh.lib.log.Logger.MSG_WRITE_LOG;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock; // For time-based flushing
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;

import com.zzh.lib.core.lang.HStrUtils;
import com.zzh.lib.core.model.LogInfo;
import com.zzh.lib.core.utils.LogUtils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * Created by zzh on 2025/3/25.
 *
 * @date: 2025/3/25 10:58
 * @email: zzh_hz@126.com
 * @author: zzh
 * @description: Optimized Runnable for writing logs to a file using BufferedWriter,
 * focusing on integrity, efficiency, and minimal performance impact.
 */
public class WriteLogRunnable implements Runnable {
    private static final String THREAD_NAME = "zzh-log-writer";
    // Define buffer size for BufferedWriter. Typical default is 8192 chars.
    // Adjust based on typical log line length and desired flush frequency.
    private static final int BUFFER_WRITER_SIZE = 8 * 1024; // 8KB buffer

    // Configuration for flushing behavior
    private static final long MAX_LOG_BATCH_SIZE_BYTES = 4 * 1024; // Flush if accumulated data exceeds this (approx)
    private static final long AUTO_FLUSH_INTERVAL_MS = 5 * 1000;  // Auto flush periodically (e.g., every 5 seconds)

    private Handler logHandler;
    private final Logger logger;
    private String currentFilePath;
    private final Object fileLock = new Object();

    private BufferedWriter bufferedWriter;
    private OutputStreamWriter outputStreamWriter;
    private FileOutputStream fileOutputStream;
    private HandlerThread logWriterThread;

    private long lastFlushTimeMs = 0;
    private long unFlushedBytes = 0; // Approximate count of bytes written since last flush

    public WriteLogRunnable(Logger logger) {
        Objects.requireNonNull(logger, "Logger cannot be null");
        this.logger = logger;
    }

    @WorkerThread
    private void setCurrentFilePath(String filePath) {
        this.currentFilePath = filePath;
    }

    @WorkerThread
    private boolean isFileInitializationNeeded(String newPath) {
        if (logger.isExceptionLogEnabled()) {
            LogUtils.d("-----zzh-log: isFileInitializationNeeded - Current file: " + currentFilePath);
            LogUtils.d("-----zzh-log: isFileInitializationNeeded - New file: " + newPath);
        }
        return !TextUtils.equals(newPath, currentFilePath) || bufferedWriter == null;
    }

    /**
     * Flushes the buffered writer to the underlying stream and then syncs the file descriptor
     * to ensure data is physically written to disk. This is a more costly operation.
     *
     * @param forceSync If true, forces file system sync.
     */
    @WorkerThread
    private void flushAndSyncWriter(boolean forceSync) {
        synchronized (fileLock) {
            if (bufferedWriter != null) {
                try {
                    bufferedWriter.flush(); // Flush BufferedWriter's internal buffer
                    unFlushedBytes = 0; // Reset counter
                    if (forceSync && fileOutputStream != null) {
                        fileOutputStream.getFD().sync(); // Ensure data is written to physical device
                        if (logger.isExceptionLogEnabled()) {
                            LogUtils.d("-----zzh-log: Log flushed and synced to file.");
                        }
                    } else if (logger.isExceptionLogEnabled()) {
                        LogUtils.d("-----zzh-log: Log flushed to OS buffer.");
                    }
                    lastFlushTimeMs = SystemClock.elapsedRealtime();
                } catch (IOException e) {
                    LogUtils.e("-----zzh-log: Failed to flush/sync log to file."+ e);
                    // Consider closing and re-initializing on next write if IOExceptions persist
                    closeFileResourcesInternal();
                }
            }
        }
    }

    public Handler getLogHandler() {
        if (logHandler == null && logWriterThread != null && logWriterThread.isAlive()) {
            logHandler = new Handler(logWriterThread.getLooper()) {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    switch (msg.what) {
                        case MSG_FLUSH_LOG:
                            // This external request should force a full sync
                            flushAndSyncWriter(true);
                            break;
                        case MSG_WRITE_LOG:
                            LogInfo logInfo = (LogInfo) msg.obj;
                            if (logInfo != null) {
                                processLogInfo(logInfo);
                            } else if (logger.isExceptionLogEnabled()) {
                                LogUtils.w("-----zzh-log: Received null LogInfo object.");
                            }
                            // Check if auto-flush is needed after writing
                            checkAndPerformAutoFlush();
                            break;
                        case MSG_RELEASE:
                            releaseResources();
                            break;
                        default:
                            super.handleMessage(msg);
                    }
                }
            };
        } else if (logWriterThread == null || !logWriterThread.isAlive()) {
            LogUtils.e("-----zzh-log: Log writer thread is not alive. Cannot get handler.");
            return null;
        }
        return logHandler;
    }

    @Override
    public void run() {
        if (logWriterThread == null) {
            logWriterThread = new HandlerThread(THREAD_NAME);
            logWriterThread.start();
            // Initialize lastFlushTimeMs once the thread starts and handler is ready
            lastFlushTimeMs = SystemClock.elapsedRealtime();
            if (logger.isExceptionLogEnabled()) {
                LogUtils.i("-----zzh-log: Log writer thread started: " + THREAD_NAME);
            }
        } else if (logger.isExceptionLogEnabled()) {
            LogUtils.w("-----zzh-log: Log writer thread already started.");
        }
    }

    @WorkerThread
    private boolean tryInitializeFileResources(String filePath) {
        synchronized (fileLock) {
            setCurrentFilePath(filePath);
            closeFileResourcesInternal(); // Close existing ones first

            if (logger.isExceptionLogEnabled()) {
                LogUtils.d("-----zzh-log: Attempting to initialize file resources for: " + filePath);
            }
            File file = new File(filePath);
            try {
                File parentDir = file.getParentFile();
                if (parentDir != null && !parentDir.exists()) {
                    if (!parentDir.mkdirs()) {
                        LogUtils.e("-----zzh-log: Failed to create parent directories for: " + filePath);
                        return false;
                    }
                }
                if (!file.exists()) {
                    if (!file.createNewFile()) {
                        LogUtils.e("-----zzh-log: Failed to create new file: " + filePath);
                        return false;
                    }
                }

                fileOutputStream = new FileOutputStream(file, true); // Append mode
                outputStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8);
                bufferedWriter = new BufferedWriter(outputStreamWriter, BUFFER_WRITER_SIZE);

                unFlushedBytes = 0; // Reset counter for new file/writer
                lastFlushTimeMs = SystemClock.elapsedRealtime(); // Reset flush timer

                if (logger.isExceptionLogEnabled()) {
                    LogUtils.d("-----zzh-log: File resources initialized for: " + filePath + " with buffer size: " + BUFFER_WRITER_SIZE);
                }
                return true;
            } catch (IOException e) {
                LogUtils.e("-----zzh-log: IOException during file resource initialization: " + filePath+ e);
            } catch (SecurityException e) {
                LogUtils.e("-----zzh-log: SecurityException: Permission denied for file: " + filePath+ e);
            }
            closeFileResourcesInternal(); // Clean up if initialization failed
            return false;
        }
    }

    @WorkerThread
    private void closeFileResourcesInternal() {
        synchronized (fileLock) { // Ensure this is also within the lock if called externally
            if (logger.isExceptionLogEnabled()) {
                LogUtils.d("-----zzh-log: Closing file resources.");
            }
            try {
                if (bufferedWriter != null) {
                    // Force flush and sync on close to ensure all data is written
                    bufferedWriter.flush();
                    if (fileOutputStream != null) {
                        try {
                            fileOutputStream.getFD().sync();
                            if (logger.isExceptionLogEnabled()) {
                                LogUtils.d("-----zzh-log: Final sync on close successful for: " + currentFilePath);
                            }
                        } catch (IOException syncEx) {
                            LogUtils.e("-----zzh-log: IOException during final sync on close for:" +
                                    " " + currentFilePath+ syncEx);
                        }
                    }
                    bufferedWriter.close(); // This also closes underlying writers/streams
                }
            } catch (IOException e) {
                if (logger.isExceptionLogEnabled()) {
                    LogUtils.e("-----zzh-log: IOException while closing BufferedWriter for: " + currentFilePath+ e);
                }
            } finally {
                bufferedWriter = null;
                outputStreamWriter = null;
                fileOutputStream = null;
                unFlushedBytes = 0;
                // currentFilePath is not nulled here as it might be needed for re-initialization logic
            }
        }
    }

    @WorkerThread
    private void writeLogStringToFile(String message, String path) {
        synchronized (fileLock) {
            if (isFileInitializationNeeded(path)) {
                if (!tryInitializeFileResources(path)) {
                    LogUtils.e("-----zzh-log: Failed to initialize file resources for path: " + path + ". Log will not be written.");
                    return;
                }
            }

            if (bufferedWriter == null) {
                LogUtils.e("-----zzh-log: BufferedWriter is null. Cannot write log to path: " + path);
                return;
            }

            try {
                bufferedWriter.write(message);
                bufferedWriter.newLine();
                // Estimate bytes written (UTF-8 can be 1-4 bytes per char, using length is an approximation)
                unFlushedBytes += message.getBytes(StandardCharsets.UTF_8).length + 1; // +1 for newline

                if (logger.isExceptionLogEnabled() && unFlushedBytes > BUFFER_WRITER_SIZE * 0.5) { // Log if buffer is getting full
                    LogUtils.d("-----zzh-log: Log entry buffered for " + path + ". Unflushed bytes (approx): " + unFlushedBytes);
                }

            } catch (IOException e) {
                LogUtils.e("-----zzh-log: IOException while writing log to file: " + path+ e);
                closeFileResourcesInternal(); // Close and attempt re-init on next write
            }
        }
    }

    /**
     * Checks if an auto-flush (not necessarily a full sync) is needed based on
     * accumulated data size or time interval.
     */
    @WorkerThread
    private void checkAndPerformAutoFlush() {
        synchronized (fileLock) { // Ensure thread safety for unFlushedBytes and lastFlushTimeMs
            if (bufferedWriter == null) {
                return;
            }

            boolean needsFlush = false;
            if (unFlushedBytes >= MAX_LOG_BATCH_SIZE_BYTES) {
                if (logger.isExceptionLogEnabled()) {
                    LogUtils.d("-----zzh-log: Auto-flushing due to batch size limit. Unflushed bytes: " + unFlushedBytes);
                }
                needsFlush = true;
            } else if ((SystemClock.elapsedRealtime() - lastFlushTimeMs) >= AUTO_FLUSH_INTERVAL_MS) {
                if (logger.isExceptionLogEnabled() && unFlushedBytes > 0) { // Only log if there's something to flush
                    LogUtils.d("-----zzh-log: Auto-flushing due to time interval. Unflushed bytes: " + unFlushedBytes);
                }
                needsFlush = (unFlushedBytes > 0); // Only flush if there's data
            }

            if (needsFlush) {
                // For auto-flush, we typically don't need to force a full disk sync every time.
                // The OS will eventually write it. A full sync is reserved for MSG_FLUSH_LOG or on close.
                flushAndSyncWriter(false); // false: do not force sync, let OS handle it for efficiency
            }
        }
    }


    @WorkerThread
    private void processLogInfo(LogInfo info) {
        if (info == null) {
            if (logger.isExceptionLogEnabled()) {
                LogUtils.w("-----zzh-log: processLogInfo called with null LogInfo.");
            }
            return;
        }
        if (HStrUtils.isEmpty(info.filePath)) {
            if (logger.isExceptionLogEnabled()) {
                LogUtils.w("-----zzh-log: LogInfo has empty file path. Log content: " + info.toString());
            }
            return;
        }
        writeLogStringToFile(info.toString(), info.filePath);
    }

    public void releaseResources() {
        if (logWriterThread != null) {
            final boolean isLogThreadAlive = logWriterThread.isAlive();
            final Looper logThreadLooper = isLogThreadAlive ? logWriterThread.getLooper() : null;

            if (logHandler != null && isLogThreadAlive && logThreadLooper != null) {
                // Send a message to ensure pending writes are processed, then release resources on the log thread.
                // We use a custom Runnable here to ensure flushAndSyncWriter(true) is called before quitting.
                logHandler.post(() -> {
                    if (logger.isExceptionLogEnabled()) {
                        LogUtils.d("-----zzh-log: Processing final flush and release on worker thread.");
                    }
                    flushAndSyncWriter(true); // Ensure all data is synced before closing
                    releaseResourcesOnWorkerThreadInternal(); // Then close file handles
                });
            } else {
                if (logger.isExceptionLogEnabled()) {
                    LogUtils.w("-----zzh-log: Log handler/thread not fully available for graceful release. Attempting direct cleanup.");
                }
                // Fallback: Perform cleanup directly. This is less ideal as it might not be on the log thread.
                // However, fileLock will still protect the shared resources.
                flushAndSyncWriter(true); // Attempt to flush and sync
                releaseResourcesOnWorkerThreadInternal(); // Attempt to close
            }

            // Quit the looper and thread
            if (isLogThreadAlive) logWriterThread.quitSafely();

            if (isLogThreadAlive) {
                try {
                    logWriterThread.join(1500); // Increased timeout slightly
                    if (logWriterThread.isAlive()) {
                        LogUtils.w("-----zzh-log: Log writer thread did not terminate in time.");
                    }
                } catch (InterruptedException e) {
                    LogUtils.e("-----zzh-log: Interrupted while waiting for log writer thread to " +
                            "finish. "+ e);
                    Thread.currentThread().interrupt();
                }
            }

            logWriterThread = null;
            logHandler = null;

            if (logger.isExceptionLogEnabled()) {
                LogUtils.i("-----zzh-log: Log writer thread and resources released sequence initiated.");
            }
        } else if (logger.isExceptionLogEnabled()){
            LogUtils.d("-----zzh-log: Release requested, but log writer thread was already null.");
        }
    }

    /**
     * Internal method to close file resources. Should be called by the log worker thread
     * or under the protection of fileLock.
     */
    @WorkerThread
    private void releaseResourcesOnWorkerThreadInternal() {
        // The actual flush and sync should have happened just before this in releaseResources()
        synchronized (fileLock) {
            closeFileResourcesInternal(); // Just closes the streams
        }
        if (logger.isExceptionLogEnabled()) {
            LogUtils.d("-----zzh-log: File resources (streams) closed on worker thread.");
        }
    }
}
